/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2011 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See doc/LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici <pronovic@ieee.org>
 * Language : Java 6
 * Project  : Secret Santa Exchange
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.santa.client.rpc.util;

import java.util.ArrayList;
import java.util.List;

import com.cedarsolutions.exception.InvalidDataException;
import com.cedarsolutions.exception.RpcSecurityException;
import com.cedarsolutions.santa.client.SantaExchangeMessages;
import com.cedarsolutions.santa.client.common.widget.WidgetUtils;
import com.cedarsolutions.santa.shared.domain.ErrorDescription;
import com.cedarsolutions.web.metadata.HttpStatusCode;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.StatusCodeException;

/**
 * Abstract class that all RPC callbacks inherit from.
 * @param <T> Return type of the asynchronous RPC callback.
 * @author Kenneth J. Pronovici <pronovic@ieee.org>
 */
public abstract class AsyncRpcCallback<T> implements AsyncCallback<T> {

    /** Localized messages. */
    private SantaExchangeMessages messages;

    /** Name of the RPC that is being invoked, like "IClientSessionRpc". */
    private String rpc;

    /** Name of the method that is being invoked, like "establishClientSession". */
    private String method;

    /**
     * Create a new callback.
     * @param rpc      Name of the RPC, like "IClientSessionRpc"
     * @param method   Name of the method, like "establishClientSession"
     */
    protected AsyncRpcCallback(String rpc, String method) {
        this.messages = GWT.create(SantaExchangeMessages.class);
        this.rpc = rpc;
        this.method = method;
    }

    /**
     * Invoked when an asynchronous call completes successfully.
     * Child classes override onSucessResult(), not onSuccess().
     */
    @Override
    public void onSuccess(T result) {
        WidgetUtils.getInstance().hideProgressIndicator();  // just in case it's showing
        this.onSuccessResult(result);
    }

    /**
     * Invoked when an asynchronous call fails to complete normally.
     * Child classes should override onValidationError() or onUnhandledError() instead of onFailure().
     * @param caught  Exception that was caught
     */
    @Override
    public void onFailure(Throwable caught) {
        try {
            WidgetUtils.getInstance().hideProgressIndicator();  // just in case it's showing
            throw caught;
        } catch (InvalidDataException error) {
            if (!this.onValidationError(error)) {
                this.onUnhandledError(caught);
            }
        } catch (Throwable error) {
            this.onUnhandledError(caught);
        }

    }

    /**
     * Invoked when an asynchronous call results in success.
     * Child classes must override this method to implement specialized success behavior.
     * @param result Result returned from the asynchronous call
     */
    protected abstract void onSuccessResult(T result);

    /**
     * Invoked when an asynchronous call results in a validation error.
     * By default, validation errors are handled the same as any other error.
     * Child classes may override this method to implement specialized validation-related behavior.
     * @param caught  Validation error that was caught
     * @return True if the validation error was handled, false otherwise.
     */
    protected boolean onValidationError(InvalidDataException caught) {
        return false;
    }

    /**
     * Invoked when an asynchronous call results in an unhandled error.
     * Child classes may override this method to implement specialized error-handling behavior.
     * @param caught  Unhandled error that was caught
     */
    protected void onUnhandledError(Throwable caught) {
        ErrorDescription error = generateError(caught);
        WidgetUtils.getInstance().showErrorPopup(error);
    }

    /** Generate the correct error for a caught exception. */
    protected ErrorDescription generateError(Throwable exception) {
        try {
            throw exception;
        } catch (StatusCodeException e) {
            HttpStatusCode statusCode = HttpStatusCode.convert(e.getStatusCode());
            switch(statusCode) {
            case FORBIDDEN:
                return this.generateNotAuthorizedError(statusCode);
            default:
                return this.generateRpcError(exception, statusCode);
            }
        } catch (RpcSecurityException e) {
            return this.generateNotAuthorizedError(e);
        } catch (Throwable e) {
            return this.generateRpcError(exception, null);
        }
    }

    /** Generate an error due to HTTP FORBIDDEN. */
    protected ErrorDescription generateNotAuthorizedError(HttpStatusCode statusCode) {
        String rpcMethod = this.rpc + "." + this.method + "()";
        String message = this.messages.abstractRpcCallback_notAuthorizedRpcErrorMessage();

        List<String> supporting = new ArrayList<String>();
        supporting.add(this.messages.abstractRpcCallback_rpcName(rpcMethod));
        if (statusCode != null) {
            supporting.add(this.messages.abstractRpcCallback_httpStatusCode(statusCode, statusCode.getValue()));
        }

        // Note: for a security error, we don't show the exception stack trace.
        return new ErrorDescription(message, supporting);
    }

    /** Generate an error due to a security exception. */
    protected ErrorDescription generateNotAuthorizedError(RpcSecurityException exception) {
        String rpcMethod = this.rpc + "." + this.method + "()";
        String message = this.messages.abstractRpcCallback_notAuthorizedRpcErrorMessage();

        List<String> supporting = new ArrayList<String>();
        supporting.add(this.messages.abstractRpcCallback_rpcName(rpcMethod));
        supporting.add(this.messages.abstractRpcCallback_securityException());
        supporting.add(this.messages.abstractRpcCallback_message(exception.getMessage()));

        // Note: for a security error, we don't show the exception stack trace.
        return new ErrorDescription(message, supporting);
    }

    /** Generate an error due to a general exception. */
    protected ErrorDescription generateRpcError(Throwable exception, HttpStatusCode statusCode) {
        String rpcMethod = this.rpc + "." + this.method + "()";
        String message = this.messages.abstractRpcCallback_generalRpcErrorMessage();

        List<String> supporting = new ArrayList<String>();
        supporting.add(this.messages.abstractRpcCallback_rpcName(rpcMethod));
        if (statusCode != null && statusCode != HttpStatusCode.UNKNOWN) {
            supporting.add(this.messages.abstractRpcCallback_httpStatusCode(statusCode, statusCode.getValue()));
        }

        return new ErrorDescription(message, exception, supporting);
    }

    public String getRpc() {
        return this.rpc;
    }

    public String getMethod() {
        return this.method;
    }

}
